package com.tianbao54.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author ZHANGYUPENG
 */
public class NioServer {

    private static final int CAPACITY = 1024;
    private static final ByteBuffer receiveBuffer = ByteBuffer.allocate(CAPACITY);
    private static final ByteBuffer sendBuffer = ByteBuffer.allocate(CAPACITY);
    private static final Map<String,SocketChannel> channels = new ConcurrentHashMap<>();
    private static final Map<String,String> pairingChannel = new ConcurrentHashMap<>();
    private static final Map<String,String> pairedChannel = new ConcurrentHashMap<>();

    private void init () {

        try (ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
             Selector selector = Selector.open()) {

            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.socket().bind(new InetSocketAddress("9.181.61.40",8888));
            serverSocketChannel.socket().setReuseAddress(true);
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            handler(selector);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void handler (Selector selector) {

        for (;;) {
            try {
                if (selector.select() <= 0)
                    continue;
                for (Iterator<SelectionKey> iterator = selector.selectedKeys().iterator() ; iterator.hasNext();) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    if (key.isAcceptable()) {
                        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                        SocketChannel socketChannel = serverSocketChannel.accept();
                        socketChannel.configureBlocking(false);
                        socketChannel.register(selector,SelectionKey.OP_READ);
                        sendMessageToChannel(socketChannel,sendBuffer,"已经与服务器连接成功，当前在线的人有：" + channels.keySet() + "\n");
                        sendMessageToChannel(socketChannel,sendBuffer,"请输入您的用户名，以便其它用户与您连接：");
                    }
                    if (key.isReadable()) {
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        String receiveMessage = receiveFromChannel(socketChannel,receiveBuffer);
                        if (null != receiveMessage)
                            setChannelAndistributionMessage(socketChannel,receiveMessage);
                        System.out.println("client : " + receiveMessage);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void setChannelAndistributionMessage (SocketChannel socketChannel,String message) {

        if (!channels.containsValue(socketChannel)) {
            message = message.indexOf("\n") != -1 ? message.replace("\n","") : message;
            channels.put(message,socketChannel);
            sendMessageToChannel(socketChannel,sendBuffer,"您好["+message+"]当前与所有人对话，也可以选择与指定人对话如：(connect zhangsan)\n");
            message = "用户：" + message + " 已上线...\n";
        }

        String key = getKeyfromMap(channels,socketChannel);

        if (message.indexOf("connect") != -1) {
            String[] str = message.split("connect");
            if (channels.containsKey(str[1].trim())) {
                sendMessageToChannel(socketChannel,sendBuffer,"正在发起请求，等待对方同意...\n");
                sendMessageToChannel(channels.get(str[1].trim()),sendBuffer,"[" + key + "] 想与你建立连接是否同意？ y/n ");
                pairingChannel.put(str[1].trim(),key);
                pairedChannel.put(str[1].trim(),"");
            }
        } else if (pairedChannel.containsKey(key) && "".equals(pairedChannel.get(key))) {
            if ("y".equalsIgnoreCase(message.trim())) {
                pairedChannel.put(key,pairingChannel.get(key));
                pairingChannel.remove(key);
                sendMessageToChannel(channels.get(key),sendBuffer,"已经与[" + pairedChannel.get(key) + "建立连接\n");
                sendMessageToChannel(channels.get(pairedChannel.get(key)),sendBuffer,"已经与[" + key + "建立连接\n");
            } else {
                sendMessageToChannel(channels.get(key),sendBuffer,"已经拒绝与[" + pairingChannel.get(key) + "建立连接\n");
                sendMessageToChannel(channels.get(pairingChannel.get(key)),sendBuffer,"对方拒绝建立连接\n");
                pairedChannel.remove(key);
                pairingChannel.remove(key);
            }
        } else if (pairedChannel.containsKey(key) || pairedChannel.containsValue(key)) {

            SocketChannel targetChannel;
            String pairedKey;
            if (pairedChannel.containsKey(key)) {
                pairedKey = key;
                targetChannel = channels.get(pairedChannel.get(key));
            } else {
                pairedKey = getKeyfromMap(pairedChannel,key);
                targetChannel = channels.get(pairedKey);
            }
            sendMessageToChannel(targetChannel,sendBuffer,"[private/" +key + "] " + message);
            if (message.indexOf("quit") != -1) {
                sendMessageToChannel(targetChannel,sendBuffer,"[private/" + key + "] 连接已断开\n");
                pairedChannel.remove(pairedKey);
            }

        } else {
            for (Map.Entry<String,SocketChannel> entry : channels.entrySet()) {
                synchronized (this) {
                    if (entry.getValue() != socketChannel) {
                        StringBuffer buffer = new StringBuffer().append("[public/").append(key).append("] ").append(message);
                        sendMessageToChannel(entry.getValue(),sendBuffer,buffer.toString());
                    }
                }
            }
        }
    }

    private String getKeyfromMap (Map<String,?> map,Object target) {
        return map.entrySet().stream().filter((entry) -> Objects.equals(entry.getValue(),target)).collect(Collectors.toList()).get(0).getKey();
    }

    protected static int sendMessageToChannel (SocketChannel socketChannel,ByteBuffer buffer,String message) {

        buffer.clear();
        buffer.put(message.getBytes());
        buffer.flip();
        try {
            return socketChannel.write(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return 0;
    }

    private void removeChannelFromChannels (SocketChannel socketChannel) {

        String removeKey = getKeyfromMap(channels,socketChannel);
        if (null != removeKey) {
            channels.remove(removeKey);
            for (Map.Entry<String,SocketChannel> entry : channels.entrySet()) {
                sendMessageToChannel(entry.getValue(),sendBuffer,"用户：" + removeKey + " 已经下线啦...\n");
            }
        }

    }

    private String receiveFromChannel (SocketChannel socketChannel,ByteBuffer buffer) {

        try {
            buffer.clear();
            int count = socketChannel.read(buffer);
            if (count > 0 ) {
                return new String (buffer.array(),0,count);
            } else if (count == 0) {
                return null;
            } else {
                removeChannelFromChannels(socketChannel);
                socketChannel.close();
                return null;
            }
        } catch (IOException e) {
            try {
                removeChannelFromChannels(socketChannel);
                socketChannel.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {

        new NioServer().init();
    }
}